local skynet = require "skynet"
local Room = require "race.race_room"
local player_manager = require "race.player_manager"
local proto_manager = require "common.proto_manager"
local service_helper = require "common.service_helper"
local MessageType = require "common.message_type"
local RaceSyncData = require "race.race_sync_data"
local RaceSyncManager = require "race.race_sync_manager"
local Player = require "race.player"

local race_manager = {}

local rooms = {}

local function onRaceEndProc(room,result)

    race_manager.removeRoom(room:getRoomId())

    local syncManager = room:getUserData()
    room:setUserData(nil)
    syncManager:stop()

    room:foreachPlayers(function(player)
        player_manager.removePlayer(player:getOpenID())
    end)

    local msg = {
        room_id = room:getRoomId(),
        result = result,
    }

    service_helper.send2Match(MessageType.RaceRoomEnd,msg)

end

local function onRaceTimeout(room)
    onRaceEndProc(room,proto_manager.errcode("ErrorCode_RaceTimeout"))
end

local function onReadyTimeout(room)
    onRaceEndProc(room,proto_manager.errcode("ErrorCode_ReadyTimeout"))
end

local function onRaceEnd(room)
    onRaceEndProc(room,proto_manager.ErrorCode_Successed)
end

function race_manager.getRoom(roomid)
    return rooms[roomid] or nil
end

local function cmpPlyaer(v1,v2)
    return v1.index < v2.index
end

function race_manager.createRoom(roomid,players)

    if rooms[roomid] then
        return nil
    end

    local room = Room:new(roomid)

    local syncManager = RaceSyncManager:new(room)
    room:setUserData(syncManager)

    table.sort(players,cmpPlyaer)

    for index,playerInfo in ipairs(players) do
        local token = math.random(1,100000000)
        local player = Player:new(playerInfo.open_id,roomid,index,token)
        room:addPlayer(player)

        local syncData = RaceSyncData:new(player,syncManager)
        syncManager:addSyncData(syncData)

        player_manager.addPlayer(player)
    end

    room.onRaceTimeout = onRaceTimeout
    room.onReadyTimeout = onReadyTimeout
    room.onRaceEnd = onRaceEnd

    rooms[room:getRoomId()] = room

    room:onPrepare()

    return room

end

function race_manager.removeRoom(roomid)
    if not rooms[roomid] then
        return false
    end
    rooms[roomid] = nil
    return true
end

function race_manager.onHandshake(fd,info)

    local room = race_manager.getRoom(info.room_id)
    if not room then
        skynet.error("race_manager|onHandshake|not find room! roomid:"
                .. tostring(info.room_id) .. " open_id:" .. tostring(info.open_id))
        return false
    end

    local player = room:getPlayerByOpenID(info.open_id)
    if not player then
        skynet.error("race_manager|onHandshake|not find player! roomid:"
                .. tostring(info.room_id) .. " open_id:" .. tostring(info.open_id))
        return false
    end

    if not player:checkToken(info.token) then
        skynet.error("race_manager|onHandshake|checkToken failed! roomid:"
                .. tostring(info.room_id) .. " open_id:" .. tostring(info.open_id)
                .. " token:" .. tostring(info.token))
        return false
    end

    player_manager.onOnline(info.open_id,fd)

    local syncManager = room:getUserData()

    if room:isRacing() then
        local msg = {
            room_id = room:getRoomId(),
            cur_frame_id = syncManager:getCurFrameId(),
            start_time = syncManager:getStartTime(),
        }
        player:sendTcpMessage("MessageId_NotifyRaceStart","MessageNotifyRaceStart",msg)
        return true
    else

        local ret = room:checkReady()

        if ret and room:isRacing() then

            syncManager:start()

            local msg = {
                room_id = room:getRoomId(),
                cur_frame_id = 0,
                start_time = syncManager:getStartTime(),
            }

            room:foreachPlayers(function(player)
                player:sendTcpMessage("MessageId_NotifyRaceStart","MessageNotifyRaceStart",msg)
            end)

        end

    end

end

function race_manager.onRaceEnd(room_id)

    local room = race_manager.getRoom(room_id)
    if not room then
        skynet.error("race_manager|onRaceEnd|not find room! roomid:"
                .. tostring(room_id))
        return false
    end

    return room:onEnd()

end

function race_manager.onInputFrame(room_id,open_id,frame)

    frame = frame or {}
    frame.frame_id = frame.frame_id or 0
    frame.datas = frame.datas or {}

    if (0 == frame.frame_id) or (0 == #frame.datas) then
        return false
    end

    local room = race_manager.getRoom(room_id)
    if not room then
        skynet.error("race_manager|onInputFrame|not find room! roomid:"
                .. tostring(room_id))
        return false
    end

    if not room:isRacing() then
        skynet.error("race_manager|onInputFrame|room is not racing! roomid:"
                .. tostring(room_id))
        return false
    end

    local player = room:getPlayerByOpenID(open_id)
    if not player then
        skynet.error("race_manager|onInputFrame|not find player! roomid:"
                .. tostring(room_id) .. " open_id:" .. tostring(open_id))
        return false
    end

    local syncManager = room:getUserData()
    local ok = true
    for _,d in ipairs(frame.datas) do
        if not syncManager:canInputFrame(frame.frame_id,open_id,d.index) then
            ok = false
            skynet.error("race_manager|onInputFrame|canInputFrame failed! "
                .. " index:" .. tostring(d.index)
                .. " frame_id:" .. tostring(frame.frame_id)
                .. " curFrame:" .. tostring(syncManager:getCurFrameId())
                .. " roomid:" .. tostring(room_id) .. " open_id:" .. tostring(open_id))
            break
        end
    end

    if not ok then
        return false
    end

    for _,d in ipairs(frame.datas) do
        syncManager:addInputFrame(frame.frame_id,open_id,d.index,d.data)
    end

    return true

end

function race_manager.getFrames(room_id)

    local room = race_manager.getRoom(room_id)
    if not room then
        return nil
    end

    local syncManager = room:getUserData()

    return syncManager:getFrames()

end

return race_manager